/*
The surface-physics project: http://code.google.com/p/surface-physics/
Adds real world physics based on the Bullet physics engine, to bases in the Orbiter space
flight simulation software.

Written by Abhijit Nandy
License : Apache License 2.0

Bump_BulletBase.cpp
BulletBase management implementation.

This file implements the Bump_BulletBase class.

*/


/* Class declaration */
#include "Bump_BulletBase.h"
#include "../Terrain/Bump_TerrainPatchOru.h"

#include <string>


bool Bump_BulletBase::simIsEnding = false;
double Bump_BulletBase::mTimeWarpFactor = 1;

extern char boutput[1000];

extern PhysicsEngine *phyEngine;

struct packet pkt;
DWORD baseCmd;

extern bool ORUCheck();


const double Bump_BulletBase::MAX_TERRAIN_ELEVATION = 10000.f; //10,000 m ?;
const double Bump_BulletBase::MAX_TERRAIN_GEN_INIT_TIME = 15.f;
const double Bump_BulletBase::MAX_TERRAIN_GEN_SWITCH_TIME = 0.f;

/**
 * Constructor for the class
 * Will be used later, when objects created dynamically.
 * Do NOT allocate memory here, let init() do that
 */
Bump_BulletBase::Bump_BulletBase():
        terrainGenerator(TT_PLANETCURVE),
        groundRigidBody(NULL),
        primaryBodyCollisionShape(NULL),
        primaryRigidBody(NULL),
        mFrozen(true)                        // let the bases unfreeze themselves as needed
{
    mode = BM_CREATE_BB;
    simBoxOffset.setValue(0.f, 0.f, 0.f);

    cutoff = 2.0;
}


/**
 * Free all objects first, de-allocating their memory
 * Each object will clears it handles etc and any handles it made with it's current BulletBase
 * (if a VESSEL was attached/detached midway in the sim, it should have properly cleared the base handle)
 *
 * Sets all values in base object back to default in case this object is reused
 */
Bump_BulletBase::~Bump_BulletBase()
{
    int i;

    //oapiWriteLogV("Bump_BulletBase::~Bump_BulletBase: CALLED : %s ", strBaseName);

    //Cleanup all objects, if exit() fails on a sub object, return error, but still delete it's ptr
    for(objIter = baseObjList.begin(), i=1;  objIter != baseObjList.end(); objIter++, i++){

        oapiWriteLogV("Bump_BulletBase::exit: calling exit() for object :%d : %s ",
                i, (*objIter)->strObjectName);

        //iterator is ptr to ptr to struct BaseObject, (*objIter) gives the struct BaseObject*
        delete (*objIter);
    }

    baseObjList.clear();

    // Clear primary rigid body - Todo: Per ref frame list of collision shapes is needed rather than these
    // separate freeing steps.
    // Manually delete the collision shape instead of adding it to the sim wide list
    if(primaryRigidBody) {
    	if (primaryRigidBody->getMotionState()) {
            // Motion states will be present when this base is used for moving reference frames
		    delete primaryRigidBody->getMotionState();
    	}

		btCollisionObject *obj = dynamic_cast<btCollisionObject*>(primaryRigidBody);
        m_dynamicsWorld->removeCollisionObject(obj);
        delete obj;
        delete primaryBodyCollisionShape;
	}

    // Clear ground
    if(groundRigidBody) {
       	if (groundRigidBody->getMotionState()) {
            // Motion states will be present when this base is used for moving reference frames
   		    delete groundRigidBody->getMotionState();
       	}

   		btCollisionObject *obj = dynamic_cast<btCollisionObject*>(groundRigidBody);
        m_dynamicsWorld->removeCollisionObject(obj);
        delete obj;
        delete groundShape;
   	}


    //Remove the base vessel, otherwise it will be saved to scenario file & conflict later on
    //This hangs unfortunately as all vessels have been deleted by the time we come here
    //oapiDeleteVessel(hObjBulletBase);

    //Base cleanup
    mode = BM_CREATE_BB;
    simBoxOffset.setValue(0.f, 0.f, 0.f);

    //oapiWriteLogV("Bump_BulletBase::~Bump_BulletBase: Destroyed : %s ", strBaseName);
}


/**
 * Initialize a base from its OBJHANDLE.
 * Creates a new BulletBase and inserts it in the Orbiter world
 *
 */
int
Bump_BulletBase::clbkSetClassCaps(OBJHANDLE hObj, OBJHANDLE hObjRBody)
{

    //This will change midway through the sim ?
    hObjRefBody = hObjRBody;
    hObjBase = hObj;
    oapiGetObjectName(hObjBase, strBaseName, 20);

    //oapiWriteLogV("Bump_BulletBase::clbkSetClassCaps: Initializing BASE: %s", strBaseName);


    //Create a BulletBase VESSEL at the origin of Brighton Beach,
    //get equatorial location(latitude, long location on 2d moon surface)


    //oapiGetBasePadEquPos(hObjBase, 0, &lng, &lat, &rad);//To create base at pad 1 for DEBUG

    oapiGetBaseEquPos(hObjBase, &lng, &lat, &rad);

    if(checkBaseVesselExists()){
        bool rv = oapiDeleteVessel(hObjBulletBase);

        if(!rv){
            oapiWriteLogV("Bump_BulletBase::clbkSetClassCaps: Could not get rid of base vessel : %s", strBaseName);
            return 1;
        }
    }


    //TODO: Read physics file for properties like cutoff ?
    //readPhysicsFile();

    //oapiWriteLogV("Bump_BulletBase::clbkSetClassCaps :completed");

    return 0;
}


/**
 * Some objects require orbiter positions for their Kinematic rigid bodies
 * This is the best place to get them. Else animation meshes lag the rendering mesh.
 *
 */
int
Bump_BulletBase::clbkPostStep(double SimT, double SimDT, double MJD)
{
    //oapiWriteLogV("Bump_BulletBase::clbkPostStep: %s :  entry", strBaseName);
    if (mode == BM_RUNNING) {
        for(objIter = baseObjList.begin();  objIter != baseObjList.end(); ){

            if((*objIter)->deleteMe){
                //(*objIter)->bbObj->exit(); //assuming its already been called, no need to add delay here

                oapiWriteLogV("Bump_BulletBase::poststep: Deleting object : %s", (*objIter)->strObjectName);


                objMapIter = objMap.find((*objIter)->hObjThis);
                if(objMapIter != objMap.end()){
                    oapiWriteLogV("Bump_BulletBase::poststep: Found in Control Map, DELETING (%p, %p)",
                       objMapIter->first,  objMapIter->second);

                   //Remove from map if found
                   objMap.erase(objMapIter);
                }

                //Remove from list only after controlled-map is checked
                delete (*objIter);
                objIter = baseObjList.erase(objIter);

                oapiWriteLog("Bump_BulletBase::clbkPostStep: Deletion finished");

                continue;
            }


            //Support different update rates(fast moving objects require more frequent updates)
            if((*objIter)->mPostStepFrequency == 0)
                (*objIter)->clbkPostStep(SimT, SimDT, MJD);
            else if((*objIter)->mPostStepFrequency > 0)
                (*objIter)->mPostStepFrequency--;

            objIter++; // object may be deleted

        }

    }

    //oapiWriteLogV("Bump_BulletBase::clbkPostStep: %s :  end", strBaseName);
    return 0;
}


/**
 * Updates the following:
 * 1. Opens the scenario specific basename.txt physics file from the Physics folder
 * 2. Attaches or creates the listed objects in the file over a number of sim steps
 * 3. Continues to update attachment points with transforms or send them to BB if needed
 * 4. Processes commands from all objects managed by this BB and BB itself by reading member
 *
 * TODO: Implement frequency of updates list and do not update inactive objects or static geometry objects
 */
int
Bump_BulletBase::clbkPreStep(double SimT, double SimDT, double MJD)
{

    char *line;

    //oapiWriteLogV("Bump_BulletBase::clbkPreStep: Base :%s, mode = %d", strBaseName, mode);

    switch(mode) {
    case BM_CREATE_BB:
    {
        createBaseVessel();
        break;
    }

    case BM_CHECK_TERRAIN:
    {
        // Check for a terrain generator. Some bases may have orulex in their physics world,
        // some may not(no orulex support on planet) ?
        if(ORUCheck()){

            // Stay in this state till Orulex is ready then do patch stuff
            if (SimT < MAX_TERRAIN_GEN_INIT_TIME)
                break;

            //ground = new Bump_TerrainPatchOru(2, 2, 0.000002f, 0.000002f, 0.000003f, 0.000003f, 1.f, this);

            terrainGenerator = TT_ORULEX;

            oapiWriteLog("Bump_BulletBase::clbkPreStep: Orulex is enabled !");
        }
        else{

            //ground = new Bump_TerrainPatch(2, 2, 0.000002f, 0.000002f, 0.000003f, 0.000003f, 1.f, this);
            // Do not create Orulex ground here, calls to terrain gen here when the ref body is not in focus will
            // lead to wrong terrain generation.

            oapiWriteLogV("Bump_BulletBase::clbkPreStep: No Orulex !! Following smooth planetary curve for terrain");
        }

        // These 2 are useful for fast debugging, keep them !
        //createGround(TT_BOX);
        //phyEngine->defineVehicle();


        // Create a simple unit cube which will serve as the primary focusing rigid body for this base
        btTransform tr;
        tr.setIdentity();
        tr.setOrigin(simBoxOffset + btVector3(0.f,-0.5f,0.f));

        primaryBodyCollisionShape =  new btBoxShape(btVector3(0.5, 0.5, 0.5));
        primaryRigidBody          =  phyEngine->addRigidBody(0, tr, primaryBodyCollisionShape);

        //Orulex max elevation is too high - need to check where this is used, probably
        //to intercept incoming vessels
        maxTerrainRadialDist = MAX_TERRAIN_ELEVATION + radRefBody; //radial comparisons

        mode = BM_CHECK_FILE;
        break;
    }

    case BM_CHECK_FILE:
    {
        //Open base name file and start parsing it

        sprintf(boutput, "Bump/%s.txt", strBaseName);
        physicsFile = oapiOpenFile(boutput, FILE_IN , ROOT);

        if(physicsFile){
            oapiWriteLogV("Bump_BulletBase::clbkPreStep: File opened : %s", boutput);
            mode = BM_PARSE_FILE;
        }
        else{
            oapiWriteLogV("Bump_BulletBase::clbkPreStep: ERROR : File could not be opened");
            mode = BM_ERROR;
        }

        break;
    }

    case BM_PARSE_FILE:
    {
        //Start parsing file and injecting objects, store their pointers

        //Read a object from the physics file of this base, till EOF, do not check for MAX_OBJECTS here
        //Its checked later already with a warning presented if numObjects == MAX_OBJECTS
        if( physicsFile &&
            oapiReadScenario_nextline (physicsFile, line)) {

            //sprintf(boutput, "Bump_BulletBase::poststep: %s", line);
            //oapiWriteLog(boutput);

            if(strcmp(line, "BEGIN_DESC") == 0){
                readPhysicsFileDescription();
            }

            //Description read, now parse and create 1 object per step
            readPhysicsFileObject();


            //sscanf (line+9, "%s", &my_value);
        }
        else{
            //All lines read, close file
            oapiCloseFile(physicsFile, FILE_IN);
            mode = BM_RUNNING;
            //oapiWriteLogV("Bump_BulletBase::clbkPreStep: Finished reading file : %p", physicsFile);
        }

        break;
    }

    case BM_RUNNING:
    {
        //Normal updation of transforms and command processing

        //oapiWriteLogV("Bump_BulletBase::clbkPreStep: ----------- %s --------------", strBaseName);

        //Parse any base commands first
        baseCmd = bb->baseCmd;
        if(baseCmd == OAPI_KEY_SPACE) // this is the only command for now
            singleStep = true;

        //TODO:Run this less frequently, say every 10/12 frames
        rangingPass(true);

        // Check if this base's reference body has focus
        // This ensures that all bases on same planet are un-frozen, other planet's bases will be frozen
        if(oapiCameraProxyGbody() == hObjRefBody) {
            mFrozen = false;
        }
        else {
            // Base lost focus !, immediate freeze !!
            mFrozen = true;
        }

        // Update objects
        for(objIter = baseObjList.begin();  objIter != baseObjList.end(); objIter++){

            if((*objIter)->mPreStepFrequency == 0)
                (*objIter)->clbkPreStep(SimT, SimDT, MJD);
            else if((*objIter)->mPreStepFrequency > 0) // skip < 0, static objects
                (*objIter)->mPreStepFrequency--;
        }

        break;

    }
    } //end-switch


    //oapiWriteLogV("Bump_BulletBase::clbkPreStep: %s :  end", strBaseName);

    return 0;

}


/**
 * Utility function to read the description from a physics file
 */
void
Bump_BulletBase::readPhysicsFileDescription(void)
{
    char *line;

    while(physicsFile && oapiReadScenario_nextline (physicsFile, line)) {
        //oapiWriteLogV("Bump_BulletBase::readPhysicsFileDescription: %s", line);

        if(strcmp(line, "END_DESC") == 0){
            break;
        }
    }

}


/**
 * Utility function to read 1 object from a physics file and create it
 * Will return immediately if END or END_OBJECTS found
 * Skips blank lines or lines not containing ':'
 *
 * TODO: Move object creation to separate function
 */
void
Bump_BulletBase::readPhysicsFileObject(void)
{
    char *line, *sep;
    char strObjName[MAX_STRLEN], strType[MAX_STRLEN];

    //Read the next line and create the object
    while(physicsFile && oapiReadScenario_nextline (physicsFile, line)) {
        //oapiWriteLogV("Bump_BulletBase::readPhysicsFileObject: %s", line);

        //Skip blank lines and comments
        if(strlen(line) == 0 || line[0] == ';')
            continue;

        //Don't parse section labels
        if((strcmp(line, "END") == 0) ||
           (strcmp(line, "END_OBJECTS") == 0)
           ){
            break;
        }

        //Should now be an object descriptor line, check for ':', else skip
        if((sep = strchr(line, ':'))){

            //Get the name and type as strings
            strncpy(strObjName, line, sep - line);
            strObjName[sep - line] = '\0';
            strcpy(strType, sep + 1);

            /*oapiWriteLogV("Bump_BulletBase::readPhysicsFileObject: Object Name:%s, Type:%s",
                    strObjName, strType);*/

            InitialObjectData initData = {strType, strObjName, NULL, this, NULL };
            createBaseObject(initData);

        }


    }

}


/**
 * To create the base vessel
 */
int
Bump_BulletBase::createBaseVessel(void)
{
    VESSELSTATUS2 vsBB;
    VECTOR3 gposRBody, gposBase;

    //Convert to global ecliptic co-ord.
    oapiEquToGlobal(hObjBase, lng, lat, rad, &gposBase);

    //Get reference body(nearest celestial body) co-ordinates for BB rpos calc.
    oapiGetGlobalPos(hObjRefBody, &gposRBody);
    radRefBody = oapiGetSize(hObjRefBody);

    //Zero out the VESSELSTATUS2 structure to prevent unwanted vessel states
    memset(&vsBB,0,sizeof(VESSELSTATUS2));
    vsBB.version = 2;
    vsBB.status = 1; //landed
    vsBB.rbody = hObjRefBody;
    vsBB.rpos = gposBase - gposRBody;
    vsBB.surf_lng = lng;
    vsBB.surf_lat = lat;
    vsBB.surf_hdg = PI/2;


    //Create the BulletBase in a landed state with same position as the passed Base
    hObjBulletBase = oapiCreateVesselEx(strBaseName, "BulletBase", &vsBB);

    //Cast and keep proper pointers to the associated BulletBase
    vBulletBase = (VESSEL3*)(oapiGetVesselInterface(hObjBulletBase));
    bb = (BulletBase*)(vBulletBase);
    //vBulletBase->SetSize(BULLET_WORLD_SIZE);

    vBulletBase->SetSize(100.0f);

    mass = bb->GetMass();
    bb->GetWeightVector(G);  //Get gravity force using

    G /= mass;


    //Terrain creation handled in poststep(),best not to do too much be4 sim gets underway

    mode = BM_CHECK_TERRAIN;

    //TODO: Read physics file for properties like cutoff ?
    //readPhysicsFile();

    //oapiWriteLogV("Bump_BulletBase::createBaseVessel: Created BulletBase %s", strBaseName);

    return 0;
}


/**
 * Create sub class object based on a table and template
 */
inline int
Bump_BulletBase::createBaseObject(InitialObjectData& initData) {
    int rv;

    //Now create the proper subtype of Bump_BaseObject
    if(initData.strType && initData.strObjName){

        std::string s(initData.strType);
        //s = "Bump_" + s;

        //Set the base right away
        Bump_BaseObject *objptr = Bump_BaseObject::instantiate(s); //= objectFactory.createInstance(s);

        if(!objptr){

            oapiWriteLogV("Bump_BulletBase::createBaseObject: ERROR : "\
                    "objectFactory failed to create object of subclass %s for %s",
                    initData.strType, initData.strObjName);

            return 1;

        }
        else{
            baseObjList.push_back(objptr);
        }



        //Find or create strObjName and attach to base, if rv!=0, deallocate imm.
        //Separate function clbkSetClassCaps() used to find/create object as it can return an error value
        //Constructor cant.
        //If handle of object available, pass it, some classes implement without clbkSetClassCaps() without hObj
        //These will search for the vessel each time, in case they control one
        if(initData.hObj) {
            rv = objptr->clbkSetClassCaps(initData);
        }
        else {
            rv = objptr->clbkSetClassCaps(initData);  //hObj is NULL
        }

        //Increment only if object created, else de-allocation from bbObj[] will cause crash
        if(rv){
            //De-allocate!, something went wrong, do not allocate memory in constructor
            delete objptr;
            baseObjList.pop_back();

            oapiWriteLogV("Bump_BulletBase::createBaseObject: ERROR : "\
                    "clbkSetClassCaps() failed on subclass %s object: %s with rv=%d, Deleted object",
                    initData.strType, initData.strObjName, rv);


            return 2;
        }

        return 0;
    }

    oapiWriteLogV("Bump_BulletBase::createBaseObject: ERROR : "\
                        "Did not try making object of subclass %s object for %s, numObject=%d",
                        initData.strType, initData.strObjName, baseObjList.size());


    return 3;
}


/**
 * Get distances of objects from base
 * Skip attached vessels, wrapped vessels, landed vessels, vehicles with different ref body than base
 * Dont use sqrt(), compare squares directly with square of min distance.
 */
void
Bump_BulletBase::rangingPass(bool skipLanded)
{

    unsigned int i, rv;
    OBJHANDLE hObj;
    char strVesselName[MAX_STRLEN], *strVesselClassName;
    VESSEL3 *v;


    //Search the Orbiter sim for this vessel
    for (i = 0; i<oapiGetVesselCount(); i++) {

        //Assuming hObj will be non-NULL
        hObj = oapiGetVesselByIndex(i);
        oapiGetObjectName(hObj, strVesselName, MAX_STRLEN);
        v = (VESSEL3*)oapiGetVesselInterface(hObj);
        strVesselClassName = v->GetClassNameA();

        /*oapiWriteLogV("Bump_BulletBase::rangingPass(%d): %3d. Found : %s", skipLanded, i+1, strVesselName);*/


        //Skips vessels with different gravity reference than this base
        OBJHANDLE hObjGravityRef = v->GetGravityRef();
        if(hObjGravityRef != hObjRefBody){
            //oapiWriteLogV("Bump_BulletBase::rangingPass: Diff reference for %s, %d", strVesselName, d);
            continue;
        }

        //Do not skip landed ships and docked ships in any combination, docked ships may cause problems later
        /*DWORD d = v->GetFlightStatus();
        if(skipLanded && (d == 1 || d == 2 || d == 3)){        //|| d == 4 || d == 7 || d == 6 || d == 5){
            //oapiWriteLogV("Bump_BulletBase::rangingPass: Skipping %s, %d", strVesselName, d);
            continue;
        }*/

        //Skip BulletBases, MeshProxies, Orulex vessels !
        if(strcmp(strVesselClassName, "BulletBase") == 0 ||
           strcmp(strVesselClassName, "MeshProxy") == 0 ||
           strcmp(strVesselClassName, "DynamicPlanet") == 0) {     // Orulex planet terrain vessel
            //oapiWriteLogV("Bump_BulletBase::rangingPass: Skipping %s of class %s", strVesselName, strVesselClassName);
            continue;
        }

        //All clear to get altitude, bodies left are above moon & not under control & in flight
        if(v->GetAltitude() > MAX_TERRAIN_ELEVATION){
           //oapiWriteLogV("Bump_BulletBase::rangingPass: %s is too high", strVesselName);
           continue;
        }

        //Is object already under control ? Will be present in objMap if so.
        objMapIter = objMap.find(hObj);
        if(objMapIter != objMap.end()){
           // oapiWriteLogV("Bump_BulletBase::rangingPass: Found in MAP : %s", strVesselName);
            continue;
        }

        //Check if already attached to parent or child
        if(checkAttachments(v)){
            //oapiWriteLogV("Bump_BulletBase::rangingPass: Vessel %s is attached already", strVesselName);
            continue;
        }


        //We are not limiting the distance for object control
        //its cheaper to detect that the object is far away in the object class & move it to its
        //own sub-simbox than make another base to control it
        //also if 2 bases are really close to each other then we need to make them aware of each other.
        //probably have a queue to handover objects


        //Here we first check if its a Bump native object
        std::string s(strVesselClassName);

        rv = 1;
        if(Bump_BaseObject::checkRegistry(s)){
        /*    oapiWriteLogV("Bump_BulletBase::rangingPass: Adding %s(%p) to Control Map",
                                    strVesselName, hObj);*/

            InitialObjectData initData = {strVesselClassName, strVesselName, hObj, this, NULL};
            rv = createBaseObject(initData);  //DO NOT ADD Bump_

        }
        else{
            //else it must be a legacy vessel
            /*oapiWriteLogV("Bump_BulletBase::rangingPass: Adding legacy vessel %s(%p) to Control Map",
                                                strVesselName, hObj);*/

            VesselCollisionDef *vCollDef = mCollDefCache.getVesselCollisionDef(strVesselClassName);

            //mCollDefCache.printCache();

            if (vCollDef) {
                //All pre-conditions met, create the collider, DO NOT ADD Bump_
                InitialObjectData initData = {"VehicleCollider", strVesselName, hObj, this, vCollDef};
                rv = createBaseObject(initData);
            }
            else {
                oapiWriteLogV("Bump_BulletBase::rangingPass: ERROR : Could not create collider for legacy vessel %s",
                        strVesselName);
            }


        }


        //if all went well, then insert into control map
        if(rv == 0)
            objMap.insert(BaseObjectMap::value_type(hObj, baseObjList.back() ));

        //printControlMap();
    }

}


/**
 * Check if attached to anything currently, return true if so, else false
 */
bool
Bump_BulletBase::checkAttachments(VESSEL3 *v)
{
    //Check attachments to parents
    DWORD count = v->AttachmentCount(true);
    unsigned int i;

    for(i=0; i<count; i++)
        if( v->GetAttachmentStatus(v->GetAttachmentHandle(true, i)))
            return 1;

    //Check attachments to children
    count = v->AttachmentCount(false);

    for(i=0; i<count; i++)
        if( v->GetAttachmentStatus(v->GetAttachmentHandle(false, i)))
            return 1;

    return 0;
}

/**
 * Called to refresh list of active sections, priority is TO DELETE sections as much as possible
 * to keep number of active sections absolutely minimal.
 *
 * All deletions/loads based on camera position and adjacency graph.
 *
 * Graph can mark certain sections as never to be removed(such as external sections)
 *
 * 1. Loads sections from file based on camera position : should be threaded off
 * 2. Unloads them if their deleteCountdown has reached 0 : this is to keep some meshes around even after
 *    they are no longer in the PVS of the current node. Maybe needed soon.
 *
 *
 *
 */
void
Bump_BulletBase::refreshSections(void)
{

    //Convert camera position in base relative co-ordinates

    VECTOR3 rposCamera, gposCamera;

    //TODO: Move this out of here, multiple bases should make just 1 call to camera position
    oapiCameraGlobalPos(&gposCamera);


    //The bases can convert the camera position to their own relative co-ords as needed
    bb->Global2Local(gposCamera, rposCamera);



    //sprintf(oapiDebugString(), "Camera at : %f, %, %f", rposCamera.x,  rposCamera.y,  rposCamera.z);

    //Every sections has its cutoff(section unloaded after camera further than this point or most stuff unloaded
    //:idling)


    //All the Adjacency graph logic comes in here
    //only when the camera moves out of current node is there a need to update mesh/physics



}


/**
 *    Delete object midway insim due to request from another object(so this function
 *    is not used for self request with deleteMe). The external object must at least
 *    specify the Orbiter OBJHANDLE.
 *
 *    The object is setup to be deletedremoved from the control map as well as object list.
 *
 *    Generally used for VESSELs in Orbiter which DO have a OBJHANDLE
 *    Lookup object using Orbiter handle & delete resulting BaseObject* if found
 */
int
Bump_BulletBase::dropBaseObject(OBJHANDLE hObjDrop)
{
/*    sprintf(boutput, "Bump_BulletBase::dropBaseObject: Called to DROP %p", hObjDrop);
    oapiWriteLog(boutput);*/

    //printControlMap();


    objMapIter = objMap.find(hObjDrop);
    if(objMapIter != objMap.end()){
    /*   sprintf(boutput, "Bump_BulletBase::dropBaseObject: Found in MAP, setting up SELF-DELETION...");
       oapiWriteLog(boutput);*/

       (objMapIter->second)->deleteMe = true;

       return 0; //all izz well
    }


    return 1; //Object not found
}


/**
 * Define the ground for this base, offsetting it by the base's sim box
 */
int
Bump_BulletBase::createGround(TerrainType terrainType)
{
    //-----Define ground : Use plane, triangle mesh, box, or .msh file for defining the ground --------
    switch(terrainType){
        case TT_PLANE:      groundShape = new btStaticPlaneShape(btVector3(0,1,0),0); break;
        case TT_BOX:        groundShape = new btBoxShape(btVector3(200, 2.0, 200)); break;
        default: break;
    }

    if(groundShape){
        btTransform tr;
        tr.setIdentity();

        if(terrainType == TT_BOX)
            tr.setOrigin(btVector3(0.f,-2.f,0.f) + simBoxOffset);
        else
            tr.setOrigin(simBoxOffset);


        //create ground object
        groundRigidBody = phyEngine->addRigidBody(0, tr, groundShape);

    }

    //------------------------------ground defined, now other physics objects-------------------------

    phyEngine->setCameraFocus(groundRigidBody, VCM_ORBITER);


    return 0;
}


/**
 * Define the ground for this base, offsetting it by the base's sim box
 */
int
Bump_BulletBase::createGroundAtPos(TerrainType terrainType, btVector3& pos)
{
    //-----Define ground : Use plane, triangle mesh, box, or .msh file for defining the ground --------
    switch(terrainType){
        case TT_PLANE:      groundShape = new btStaticPlaneShape(btVector3(0,1,0),0); break;
        case TT_BOX:        groundShape = new btBoxShape(btVector3(200, 2.0, 200)); break;
        default: break;
    }

    if(groundShape){

        btTransform tr;
        tr.setIdentity();

        if(terrainType == TT_BOX)
            tr.setOrigin(btVector3(0.f,-2.f,0.f) + pos);
        else
            tr.setOrigin(pos);


        //create ground object
        groundRigidBody = phyEngine->addRigidBody(0, tr, groundShape);
    }

    //------------------------------ground defined, now other physics objects-------------------------

    phyEngine->setCameraFocus(groundRigidBody, VCM_ORBITER);


    return 0;
}


/**
 *
 */
bool
Bump_BulletBase::checkBaseVesselExists(void)
{
    unsigned int i;
    unsigned int numSimVessels = oapiGetVesselCount();
    OBJHANDLE hObj;
    char strVesselName[MAX_STRLEN];



    //Search the Orbiter sim for this vessel
    for (i = 0; i<numSimVessels; i++) {

        //Assuming hObj will be non-NULL
        hObj = oapiGetVesselByIndex(i);
        oapiGetObjectName(hObj, strVesselName, MAX_STRLEN);


        //Does the vessel name match this object's name ?
        if(strcmp(strVesselName, strBaseName) == 0){
            oapiWriteLogV("Bump_BulletBase::checkBaseVesselExists: Found : %s already IN SIM!!", strVesselName);
            hObjBulletBase = hObj;
            return true;
        }

    }

    return false;
}


/**
 * mode needed as the base could be transferring its ground patch pointer
 * to either the gui focus body or the orbiter focus body
 */
void
Bump_BulletBase::setCameraFocus(VisualizerCameraMode mode)
{
    phyEngine->setCameraFocus(primaryRigidBody, mode);
}


/**
 *    Prints the control map, useful to debug
 */
int
Bump_BulletBase::printControlMap(void)
{
    oapiWriteLogV("Bump_BulletBase::printControlMap: %d objects in map", objMap.size());

    sprintf(boutput, "");
    for(objMapIter = objMap.begin();  objMapIter != objMap.end(); objMapIter++)
       sprintf(boutput, "%s(%p, %p)\n", boutput, (objMapIter->first),  (objMapIter->second) );


    oapiWriteLog(boutput);

    return 0;
}


/**
 *    Prints the object list, useful to debug
 */
int
Bump_BulletBase::printObjectList(void)
{
    oapiWriteLogV("Bump_BulletBase::printObjectList: %d objects in list", baseObjList.size());

    sprintf(boutput, "");
    for(objIter = baseObjList.begin();  objIter != baseObjList.end(); objIter++)
        sprintf(boutput, "Found: %s\n", boutput, (*objIter)->strObjectName);


    oapiWriteLog(boutput);

    return 0;
}
